#include "preHeader.h"
#include "QuestLog.h"
#include "QuestStorage.h"
#include "QuestMgr.h"
#include "Map/MapInstance.h"

QuestLog::QuestLog(Player* pOwner)
: m_pOwner(pOwner)
, m_pQuestProto(NULL)
, m_pProtoPrivate(NULL)
, m_isQuestSubmitting(false)
{
}

QuestLog::~QuestLog()
{
	SAFE_DELETE(m_pProtoPrivate);
}

WheelTimerMgr *QuestLog::GetWheelTimerMgr()
{
	return &m_pOwner->GetMapInstance()->sWheelTimerMgr;
}

void QuestLog::Init(const QuestPrototype* pQuestProto)
{
	m_pQuestProto = pQuestProto;
	m_questProp.questGuid = m_pOwner->NewQuestUniqueKey();
	m_questProp.questTypeID = m_pQuestProto->questTypeID;
	if (m_pQuestProto->questTimeMax != 0) {
		m_questProp.questExpireTime =
			GET_UNIX_TIME + m_pQuestProto->questTimeMax;
	}
}

void QuestLog::Load(const QuestPrototype* pQuestProto, const inst_quest_prop& questProp)
{
	m_pQuestProto = pQuestProto;
	m_questProp = questProp;
}

void QuestLog::PostInit()
{
	m_questProp.questConditions.resize(m_pQuestProto->questConditions.size());
	RefreshQuestConditionStatus();

	for (size_t i = 0, n = m_pQuestProto->questConditions.size(); i < n; ++i) {
		auto& questCondition = m_pQuestProto->questConditions[i];
		m_questCares.set(questCondition.conditionType);
	}

	if (m_questProp.questExpireTime != 0 && CanUpdateStatus()) {
		CreateTimerX(std::bind(&QuestLog::EventQuestTimeout, this),
			SubLeastZero<s64>(GET_UNIX_TIME, m_questProp.questExpireTime) * 1000, 1);
	}
}

void QuestLog::Failed()
{
	BIT_SET(m_questProp.questFlags, inst_quest_prop::IsFailed);
	sQuestMgr.OnQuestFailed(m_pOwner, this);
	if (m_pQuestProto->questFlags.isRemoveFailed) {
		sQuestMgr.HandleCancelQuest(m_pOwner, NULL, m_questProp.questGuid);
	}
}

bool QuestLog::CanUpdateStatus(bool isRevertable) const
{
	if (m_isQuestSubmitting) {
		return false;
	}
	if (IsQuestFlagsFailed()) {
		return false;
	}
	if (IsQuestFlagsFinished()) {
		return false;
	}
	if (!isRevertable && IsQuestFinished(true)) {
		return false;
	}
	return true;
}

bool QuestLog::IsQuestFinished(bool isCheckAll) const
{
	if (IsQuestFlagsFailed()) {
		return false;
	}
	if (IsQuestFlagsFinished()) {
		return true;
	}
	if (!IsQuestConditionsReached(isCheckAll)) {
		return false;
	}
	return true;
}

bool QuestLog::IsQuestConditionsReached(bool isCheckAll) const
{
	if (!isCheckAll && m_pQuestProto->questFlags.isAnyCondition) {
		for (size_t i = 0, n = m_pQuestProto->questConditions.size(); i < n; ++i) {
			auto& questCondition = m_pQuestProto->questConditions[i];
			auto& questInstCondition = m_questProp.questConditions[i];
			if (questInstCondition.progress >= (s64)questCondition.conditionNum) {
				return true;
			}
		}
		return m_pQuestProto->questConditions.empty();
	} else {
		for (size_t i = 0, n = m_pQuestProto->questConditions.size(); i < n; ++i) {
			auto& questCondition = m_pQuestProto->questConditions[i];
			auto& questInstCondition = m_questProp.questConditions[i];
			if (questInstCondition.progress < (s64)questCondition.conditionNum) {
				return false;
			}
		}
		return true;
	}
}

bool QuestLog::SetQuestFinished()
{
	if (!CanUpdateStatus(true)) {
		return false;
	}
	BIT_SET(m_questProp.questFlags, inst_quest_prop::IsFinished);
	return true;
}

bool QuestLog::TryTriggerEvent(bool isRevertable, QuestConditionType type,
	const std::function<std::pair<OpQuestType, int64>
	(size_t, const QuestCondition&, inst_quest_prop::Condition&)>& func)
{
	if (!CanUpdateStatus(isRevertable)) {
		return false;
	}
	bool isTrigger = false;
	for (size_t i = 0, n = m_pQuestProto->questConditions.size(); i < n; ++i) {
		auto& questCondition = m_pQuestProto->questConditions[i];
		if (questCondition.conditionType != (u32)type) {
			continue;
		}
		auto& questInstCondition = m_questProp.questConditions[i];
		auto rst = func(i, questCondition, questInstCondition);
		if (rst.first == OpQuestType::None) {
			continue;
		}
		switch (rst.first) {
		case OpQuestType::Add:
			questInstCondition.progress += rst.second;
			break;
		case OpQuestType::Assign:
			questInstCondition.progress = rst.second;
			break;
		}
		if (questInstCondition.progress > (s64)questCondition.conditionNum) {
			questInstCondition.progress = (s64)questCondition.conditionNum;
		}
		isTrigger = true;
	}
	return isTrigger;
}

bool QuestLog::OnTalkNPC(Creature* pCreature)
{
	return TryTriggerEvent(false, QuestConditionType::TalkNPC, [=]
		(size_t i, const QuestCondition& questCondition, inst_quest_prop::Condition& questInstCondition)
		->std::pair<OpQuestType, int64> {
		switch ((QuestObjType)questCondition.conditionArgs[0]) {
		case QuestObjType::NPC:
			if (IS_VECTOR_CONTAIN_VALUE(questCondition.conditionIds, pCreature->GetSpawnId())) {
				return { OpQuestType::Add, 1 };
			}
			break;
		case QuestObjType::NPCPt:
			if (IS_VECTOR_CONTAIN_VALUE(questCondition.conditionIds, pCreature->GetEntry())) {
				return { OpQuestType::Add, 1 };
			}
			break;
		}
		return { OpQuestType::None, 0 };
	});
}

bool QuestLog::OnKillCreature(Creature* pCreature)
{
	return TryTriggerEvent(false, QuestConditionType::KillCreature, [=]
		(size_t i, const QuestCondition& questCondition, inst_quest_prop::Condition& questInstCondition)
		->std::pair<OpQuestType, int64> {
		switch ((QuestObjType)questCondition.conditionArgs[0]) {
		case QuestObjType::NPC:
			if (IS_VECTOR_CONTAIN_VALUE(questCondition.conditionIds, pCreature->GetSpawnId())) {
				return { OpQuestType::Add, 1 };
			}
			break;
		case QuestObjType::NPCPt:
			if (IS_VECTOR_CONTAIN_VALUE(questCondition.conditionIds, pCreature->GetEntry())) {
				return { OpQuestType::Add, 1 };
			}
			break;
		}
		return { OpQuestType::None, 0 };
	});
}

bool QuestLog::OnHaveCheque(ChequeType chequeType, uint64 chequeValue, CHEQUE_FLOW_TYPE flowType, params<uint32> flowParams)
{
	return TryTriggerEvent(true, QuestConditionType::HaveCheque, [=]
		(size_t i, const QuestCondition& questCondition, inst_quest_prop::Condition& questInstCondition)
		->std::pair<OpQuestType, int64> {
		if (questCondition.conditionIds[0] != (u32)chequeType) {
			return { OpQuestType::None, 0 };
		}
		return { OpQuestType::Assign, m_pOwner->GetCheque((ChequeType)chequeType) };
	});
}

bool QuestLog::OnHaveItem(uint32 itemTypeID, uint32 itemCount, ITEM_FLOW_TYPE flowType, params<uint32> flowParams)
{
	return TryTriggerEvent(true, QuestConditionType::HaveItem, [=](
		size_t i, const QuestCondition& questCondition, inst_quest_prop::Condition& questInstCondition)
		->std::pair<OpQuestType, int64> {
		if (!IS_VECTOR_CONTAIN_VALUE(questCondition.conditionIds, itemTypeID)) {
			return { OpQuestType::None, 0 };
		}
		auto hasItemCount = m_pOwner->GetItemStorage()->GetItemsAmount(
			questCondition.conditionIds.data(), questCondition.conditionIds.size(),
			0, (u32)questCondition.conditionNum);
		if (questCondition.conditionArgs.empty()) {
			return { OpQuestType::Assign, hasItemCount };
		}
		if (questCondition.conditionArgs[0] == (u32)flowType) {
			questInstCondition.userData += itemCount;
		}
		return { OpQuestType::Assign, std::min<s64>(questInstCondition.userData, hasItemCount) };
	});
}

bool QuestLog::OnUseItem(uint32 itemTypeID, uint32 itemCount, uint32 actionUniqueKey)
{
	return TryTriggerEvent(false, QuestConditionType::UseItem, [=](
		size_t i, const QuestCondition& questCondition, inst_quest_prop::Condition& questInstCondition)
		->std::pair<OpQuestType, int64> {
		if (!IS_VECTOR_CONTAIN_VALUE(questCondition.conditionIds, itemTypeID)) {
			return { OpQuestType::None, 0 };
		}
		if (questCondition.conditionArgs.empty()) {
			return { OpQuestType::Add, itemCount };
		}
		auto vars = m_pOwner->GetVariables().Get<LuaTable>();
		auto lastUniqueKey = vars.get<uint32>("UseItem.key?");
		if (questCondition.conditionArgs[0] == 100) {
			if (actionUniqueKey != lastUniqueKey) {
				if (questCondition.conditionArgs.size() <= 1) {
					return { OpQuestType::Add, itemCount };
				} else {
					for (size_t i = 1, n = questCondition.conditionArgs.size(); i < n; ++i) {
						if (m_pOwner->IsInGuideArea(questCondition.conditionArgs[i],
							QUEST_INTERACTIVE_DIST_MAX)) {
							return { OpQuestType::Add, itemCount };
						}
					}
				}
			}
		} else {
			if (actionUniqueKey == lastUniqueKey) {
				uint32 conditionId = 0;
				auto pTargetObject = vars.get<LocatableObject*>("UseItem.target?");
				switch ((QuestObjType)questCondition.conditionArgs[0]) {
				case QuestObjType::NPC:
					conditionId = pTargetObject->IsType(TYPE_CREATURE) ?
						((Creature*)pTargetObject)->GetSpawnId() : 0;
					break;
				case QuestObjType::SObj:
					conditionId = pTargetObject->IsType(TYPE_STATICOBJECT) ?
						((StaticObject*)pTargetObject)->GetSpawnId() : 0;
					break;
				case QuestObjType::NPCPt:
					conditionId = pTargetObject->IsType(TYPE_CREATURE) ?
						((Creature*)pTargetObject)->GetEntry() : 0;
					break;
				case QuestObjType::SObjPt:
					conditionId = pTargetObject->IsType(TYPE_STATICOBJECT) ?
						((StaticObject*)pTargetObject)->GetEntry() : 0;
					break;
				}
				if (IS_SPAN_CONTAIN_VALUE(questCondition.conditionArgs.begin() + 1,
					questCondition.conditionArgs.end(), conditionId)) {
					return { OpQuestType::Add, itemCount };
				}
			}
		}
		return { OpQuestType::None, 0 };
	});
}

bool QuestLog::OnPlayStory(bool isSucc)
{
	return TryTriggerEvent(false, QuestConditionType::PlayStory, [=]
		(size_t i, const QuestCondition& questCondition, inst_quest_prop::Condition& questInstCondition)
		->std::pair<OpQuestType, int64> {
		if (!isSucc) {
			return { OpQuestType::None, 0 };
		}
		return { OpQuestType::Add, 1 };
	});
}

void QuestLog::RefreshQuestConditionStatus()
{
	for (size_t i = 0, n = m_pQuestProto->questConditions.size(); i < n; ++i) {
		auto& questCondition = m_pQuestProto->questConditions[i];
		auto& questInstCondition = m_questProp.questConditions[i];
		switch (QuestConditionType(questCondition.conditionType)) {
		case QuestConditionType::HaveCheque:
			questInstCondition.progress =
				m_pOwner->GetCheque(ChequeType(questCondition.conditionIds[0]));
			break;
		case QuestConditionType::HaveItem:
			questInstCondition.progress = std::min<s64>(questInstCondition.userData,
				m_pOwner->GetItemStorage()->GetItemsAmount(
					questCondition.conditionIds.data(), questCondition.conditionIds.size(),
					0, (u32)questCondition.conditionNum));
			break;
		}
	}
}

void QuestLog::RefreshQuestFinishStatus()
{
	if (IsQuestFinished()) {
		m_pOwner->GetQuestStorage()->PushQuestFinishStatus(this);
		sQuestMgr.OnQuestFinish(m_pOwner, this);
	}
}

void QuestLog::EventQuestTimeout()
{
	if (CanUpdateStatus()) {
		m_pOwner->SendError(GErrorCodeP1(ErrQuestOutOfTime, MakeTextID(
			m_pQuestProto->questTypeID, STRING_TEXT_TYPE::QUEST_NAME)));
		Failed();
	}
}

void QuestLog::CleanResource4SubmitQuestAction()
{
	for (size_t i = 0, n = m_pQuestProto->questConditions.size(); i < n; ++i) {
		auto& questCondition = m_pQuestProto->questConditions[i];
		switch ((QuestConditionType)questCondition.conditionType) {
		case QuestConditionType::HaveCheque:
			if (questCondition.conditionFlags[0]) {
				m_pOwner->CostCheque(
					(ChequeType)questCondition.conditionIds[0], questCondition.conditionNum,
					CFT_QUEST_SUBMIT, {m_pQuestProto->questTypeID});
			}
			break;
		case QuestConditionType::HaveItem:
			if (questCondition.conditionFlags[0]) {
				m_pOwner->GetItemStorage()->RemoveItemsAmount(
					questCondition.conditionIds.data(), questCondition.conditionIds.size(),
					(u32)questCondition.conditionNum, IFT_QUEST_SUBMIT, {m_pQuestProto->questTypeID});
			}
			break;
		}
	}
}

void QuestLog::QueryQuestNavData(int8 navIdx)
{
	auto itr = m_questNavDatas.find(navIdx);
	if (itr != m_questNavDatas.end()) {
		SendQuestNavData(navIdx, itr->second);
		return;
	}

	QuestNavObjInst navObjInst{(u32)QuestObjType::None};
	switch (QuestNavIdx(navIdx)) {
	case QuestNavIdx::Publisher:
		QuestObjInst2QuestNavObjInst(m_pQuestProto->questPublisher, navObjInst);
		break;
	case QuestNavIdx::Verifier:
		QuestObjInst2QuestNavObjInst(m_pQuestProto->questVerifier, navObjInst);
		break;
	case QuestNavIdx::Guider:
		QuestObjInst2QuestNavObjInst(m_pQuestProto->questGuider, navObjInst);
		break;
	default:
		if (navIdx >= 0 && navIdx < m_pQuestProto->questConditions.size()) {
			QuestCondition2QuestNavObjInst(
				m_pQuestProto->questConditions[navIdx], navObjInst);
		}
		break;
	}
	if (navObjInst.objType == (u32)QuestObjType::None) {
		PushQuestNavData(navIdx, {0});
		return;
	}

	switch (QuestObjType(navObjInst.objType)) {
	case QuestObjType::Guide:
		PushQuestNavDataByGuide(navIdx, navObjInst);
		break;
	case QuestObjType::NPC:
		PushQuestNavDataByNPC(navIdx, navObjInst);
		break;
	case QuestObjType::SObj:
		PushQuestNavDataBySObj(navIdx, navObjInst);
		break;
	case QuestObjType::NPCPt:
		PushQuestNavDataByNPCPt(navIdx, navObjInst);
		break;
	case QuestObjType::SObjPt:
		PushQuestNavDataBySObjPt(navIdx, navObjInst);
		break;
	default:
		assert(false && "can't reach here.");
		break;
	}
}

void QuestLog::PushQuestNavData(int8 navIdx, const QuestNavData& navData, int flags)
{
	if ((flags & QuestNavData::PREVENT_SYNC_SERVER) == 0) {
		m_questNavDatas[navIdx] = navData;
	}
	if ((flags & QuestNavData::PREVENT_SYNC_CLIENT) == 0) {
		SendQuestNavData(navIdx, navData);
	}
}

void QuestLog::PushQuestNavDataByGuide(int8 navIdx, const QuestNavObjInst& navObjInst)
{
	auto pGuide = GetDBEntry<LandmarkPoint>(navObjInst.objID);
	if (pGuide == NULL) {
		PushQuestNavData(navIdx, {0});
		return;
	}
	QuestNavData navData;
	navData.mapId = pGuide->map_id;
	navData.mapType = pGuide->map_type;
	navData.mapLine = -1;
	navData.pos = {pGuide->x, pGuide->y, pGuide->z};
	PushQuestNavData(navIdx, navData);
}

void QuestLog::PushQuestNavDataByNPC(int8 navIdx, const QuestNavObjInst& navObjInst)
{
	auto pSpawn = GetDBEntry<CreatureSpawn>(navObjInst.objID);
	if (pSpawn == NULL) {
		PushQuestNavData(navIdx, {0});
		return;
	}
	QuestNavData navData;
	navData.mapId = pSpawn->map_id;
	navData.mapType = pSpawn->map_type;
	navData.mapLine = -1;
	navData.pos = {pSpawn->x, pSpawn->y, pSpawn->z};
	PushQuestNavData(navIdx, navData);
}

void QuestLog::PushQuestNavDataBySObj(int8 navIdx, const QuestNavObjInst& navObjInst)
{
	auto pSpawn = GetDBEntry<StaticObjectSpawn>(navObjInst.objID);
	if (pSpawn == NULL) {
		PushQuestNavData(navIdx, {0});
		return;
	}
	QuestNavData navData;
	navData.mapId = pSpawn->map_id;
	navData.mapType = pSpawn->map_type;
	navData.mapLine = -1;
	navData.pos = {pSpawn->x, pSpawn->y, pSpawn->z};
	PushQuestNavData(navIdx, navData);
}

template <typename SPAWN>
const SPAWN* FilterBestSpawnInstance(
	const CTableCache<SPAWN>* pTable, uint32 entry, Player* pOwner)
{
	std::vector<const SPAWN*> availSpawnsList[3];
	for (auto& pair : MakeIteratorRange(pTable->Begin(), pTable->End())) {
		auto pSpawn = &pair.second;
		if (pSpawn->entry == entry) {
			if (pSpawn->map_id == pOwner->GetMapId() &&
				pSpawn->map_type == pOwner->GetMapType()) {
				availSpawnsList[0].push_back(pSpawn);
				continue;
			}
			if (!availSpawnsList[0].empty()) {
				continue;
			}
			if (pSpawn->map_id == pOwner->GetMapId()) {
				availSpawnsList[1].push_back(pSpawn);
				continue;
			}
			if (!availSpawnsList[1].empty()) {
				continue;
			}
			availSpawnsList[2].push_back(pSpawn);
			continue;
		}
	}
	for (auto& availSpawns : availSpawnsList) {
		if (!availSpawns.empty()) {
			return availSpawns[System::Rand(0, (int)availSpawns.size())];
		}
	}
	return NULL;
}

template <typename SPAWN>
void PushQuestNavDataBySpawnInstance(
	const SPAWN* pSpawn, Player* pOwner, uint32 questGuid, uint32 navIdx)
{
	auto pQuestStorage = pOwner->GetQuestStorage();
	auto pQuestLog = pQuestStorage->GetQuestLogByKey(questGuid);
	if (pQuestLog == NULL) {
		return;
	}
	if (pSpawn == NULL) {
		pQuestLog->PushQuestNavData(navIdx, {0});
		return;
	}
	QuestNavData navData;
	navData.mapId = pSpawn->map_id;
	navData.mapType = pSpawn->map_type;
	navData.mapLine = -1;
	navData.pos = {pSpawn->x, pSpawn->y, pSpawn->z};
	pQuestLog->PushQuestNavData(navIdx, navData);
}

static void PushQuestNavDataByRPCRst(
	INetStream& pck, int32 err, Player* pOwner, uint32 questGuid, uint32 navIdx)
{
	auto pQuestStorage = pOwner->GetQuestStorage();
	auto pQuestLog = pQuestStorage->GetQuestLogByKey(questGuid);
	if (pQuestLog == NULL) {
		return;
	}
	if (err != RPCErrorNone || !pck.Read<bool>()) {
		pQuestLog->PushQuestNavData(
			navIdx, {u32(-1)}, QuestNavData::PREVENT_SYNC_SERVER);
		return;
	}
	InstGUID instGuid;
	vector3f pos;
	pck >> instGuid >> pos;
	QuestNavData navData;
	navData.mapId = instGuid.MAPID;
	navData.mapType = instGuid.TID;
	navData.mapLine = instGuid.UID;
	navData.pos = pos;
	pQuestLog->PushQuestNavData(navIdx, navData);
}

void QuestLog::PushQuestNavDataByNPCPt(int8 navIdx, const QuestNavObjInst& navObjInst)
{
	auto questGuid = m_questProp.questGuid;
	auto pProto = GetDBEntry<CharPrototype>(navObjInst.objID);
	if (!pProto->charFlags.isActivity) {
		sAsyncTaskMgr.AddTask(CreateAsyncTask<const CreatureSpawn*>(
			[=, pOwner = m_pOwner](const CreatureSpawn*& pSpawn) {
			FilterBestSpawnInstance(
				sDBMgr.GetTable<CreatureSpawn>(), pProto->charTypeId, pOwner);
		}, [=](AsyncTaskOwner* pOwner, const CreatureSpawn*& pSpawn) {
			PushQuestNavDataBySpawnInstance(
				pSpawn, (Player*)pOwner, questGuid, navIdx);
		}), m_pOwner);
	} else {
		NetPacket rpcReqPck(MS_QUERY_ACTIVITY_OBJECT_POSITION);
		rpcReqPck << TYPE_CREATURE
			<< navObjInst.objID << m_pOwner->GetInstGuid();
		m_pOwner->RPCInvoke2Gs(rpcReqPck,
			[=, pOwner = m_pOwner](INetStream& pck, int32 err, bool) {
			PushQuestNavDataByRPCRst(pck, err, pOwner, questGuid, navIdx);
		}, DEF_S2S_RPC_TIMEOUT);
	}
}

void QuestLog::PushQuestNavDataBySObjPt(int8 navIdx, const QuestNavObjInst& navObjInst)
{
	auto questGuid = m_questProp.questGuid;
	auto pProto = GetDBEntry<SObjPrototype>(navObjInst.objID);
	if (!pProto->sobjFlags.isActivity) {
		sAsyncTaskMgr.AddTask(CreateAsyncTask<const StaticObjectSpawn*>(
			[=, pOwner = m_pOwner](const StaticObjectSpawn*& pSpawn) {
			FilterBestSpawnInstance(
				sDBMgr.GetTable<StaticObjectSpawn>(), pProto->sobjTypeId, pOwner);
		}, [=](AsyncTaskOwner* pOwner, const StaticObjectSpawn*& pSpawn) {
			PushQuestNavDataBySpawnInstance(
				pSpawn, (Player*)pOwner, questGuid, navIdx);
		}), m_pOwner);
	} else {
		NetPacket rpcReqPck(MS_QUERY_ACTIVITY_OBJECT_POSITION);
		rpcReqPck << TYPE_STATICOBJECT
			<< navObjInst.objID << m_pOwner->GetInstGuid();
		m_pOwner->RPCInvoke2Gs(rpcReqPck,
			[=, pOwner = m_pOwner](INetStream& pck, int32 err, bool) {
			PushQuestNavDataByRPCRst(pck, err, pOwner, questGuid, navIdx);
		}, DEF_S2S_RPC_TIMEOUT);
	}
}

void QuestLog::QuestObjInst2QuestNavObjInst(
	const QuestObjInst& objInst, QuestNavObjInst& navObjInst) const
{
	navObjInst.objType = objInst.objType;
	navObjInst.objID = objInst.objID;
	navObjInst.objArgType = QuestNavObjInst::ArgType::None;
}

void QuestLog::QuestCondition2QuestNavObjInst(
	const QuestCondition& questCondition, QuestNavObjInst& navObjInst) const
{
	switch (QuestConditionType(questCondition.conditionType)) {
	case QuestConditionType::KillCreature:
		navObjInst.objType = questCondition.conditionArgs[0];
		navObjInst.objID = RandomVectorValue(questCondition.conditionIds);
		navObjInst.objArgType = QuestNavObjInst::ArgType::None;
		break;
	case QuestConditionType::HaveCheque:
		QuestObjInst2QuestNavObjInst(questCondition.forNavInfo, navObjInst);
		break;
	case QuestConditionType::HaveItem:
		QuestObjInst2QuestNavObjInst(questCondition.forNavInfo, navObjInst);
		break;
	default:
		navObjInst.objType = (u32)QuestObjType::None;
		break;
	}
}

uint32 QuestLog::GetQuestLackItemCount(uint32 itemTypeID) const
{
	uint32 lackItemCount = 0;
	for (size_t i = 0, n = m_pQuestProto->questConditions.size(); i < n; ++i) {
		auto& questCondition = m_pQuestProto->questConditions[i];
		auto& questInstCondition = m_questProp.questConditions[i];
		switch (QuestConditionType(questCondition.conditionType)) {
		case QuestConditionType::HaveItem:
			if (IS_VECTOR_CONTAIN_VALUE(questCondition.conditionIds, itemTypeID)) {
				auto hasItemCount = m_pOwner->GetItemStorage()->GetItemsAmount(
					questCondition.conditionIds.data(), questCondition.conditionIds.size(),
					ISF_INCLUDE_BANK | ISF_INCLUDE_EQUIP, (u32)questCondition.conditionNum);
				lackItemCount = std::max(lackItemCount,
					(u32)SubLeastZero<u64>(questCondition.conditionNum, hasItemCount));
			}
			break;
		}
	}
	return lackItemCount;
}

void QuestLog::SendNewQuestInstance() const
{
	NetPacket pack(SMSG_CREATE_QUEST);
	m_questProp.Save(pack);
	m_pOwner->SendPacket(pack);
}

void QuestLog::SendUpdateQuestStatus() const
{
	NetPacket pack(SMSG_UPDATE_QUEST);
	m_questProp.Save(pack);
	m_pOwner->SendPacket(pack);
}

void QuestLog::SendQuestNavData(int8 navIdx, const QuestNavData& navData) const
{
	NetPacket pack(SMSG_QUERY_QUEST_NAVIGATION_RESP);
	pack << m_questProp.questGuid << navIdx;
	pack << navData.mapId << navData.mapType << navData.mapLine << navData.pos;
	m_pOwner->SendPacket(pack);
}
